package com.sxkj.common.utils;

import com.github.wxpay.sdk.WXPayUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Map;

/**
 * ClassName:RequestUtil
 * Function: HTTP请求类，携带证书和不带证书
 * Date:     2019-12-27 下午3:22:33
 *
 * @author Zhang Xingliang
 */
public class RequestUtil {

    private static int connectTimeoutMs = 8000, readTimeoutMs = 10000;

    /**
     * 请求，只请求一次，不做重试
     *
     * @param url
     * @param data
     * @param connectTimeoutMs
     * @param readTimeoutMs
     * @param useCert          是否使用证书，针对退款、撤销等操作
     * @return
     * @throws Exception
     */
    private static String requestOnce(final String url, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert
            , InputStream certStream, String password) throws Exception {

        BasicHttpClientConnectionManager connManager = useCert ? setSSLSocketFactory(certStream, password) : setSocketFactory();

        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connManager)
                .build();

        HttpPost httpPost = new HttpPost(url);

        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
        httpPost.setConfig(requestConfig);

        StringEntity postEntity = new StringEntity(data, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);

        HttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity httpEntity = httpResponse.getEntity();
        return EntityUtils.toString(httpEntity, "UTF-8");

    }

    private static BasicHttpClientConnectionManager setSocketFactory() {
        return new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", SSLConnectionSocketFactory.getSocketFactory())
                        .build(),
                null,
                null,
                null
        );
    }

    private static BasicHttpClientConnectionManager setSSLSocketFactory(InputStream certStream, String password) throws Exception {
        // 证书
        char[] pwd = password.toCharArray();
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(certStream, pwd);
        // 实例化密钥库 & 初始化密钥工厂
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, pwd);

        // 创建 SSLContext
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());

        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                sslContext,
                new String[]{"TLSv1"},
                null,
                new DefaultHostnameVerifier());

        return new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", sslConnectionSocketFactory)
                        .build(),
                null,
                null,
                null
        );
    }


    private static String request(String url, Map<String, String> data, int connectTimeoutMs, int readTimeoutMs
            , boolean useCert, InputStream certStream, String password) throws Exception {
        // String msgUUID = data.get("nonce_str");//用于上报数据
        String reqBody = XmlUtils.mapToXml(data);
        try {
            return requestOnce(url, reqBody, connectTimeoutMs, readTimeoutMs, useCert, certStream, password);
        } catch (UnknownHostException e) {  // dns 解析错误，或域名不存在
            throw e;
        } catch (ConnectTimeoutException e) {
            throw e;
        } catch (SocketTimeoutException e) {
            throw e;
        } catch (Exception e) {
            throw e;
        }
    }


    /**
     * 可重试的，非双向认证的请求
     *
     * @param url
     * @param data
     * @return
     */
    public static String requestWithoutCert(String url, Map<String, String> data) throws Exception {
        return request(url, data, connectTimeoutMs, readTimeoutMs, false, null, null);
    }

    /**
     * 可重试的，非双向认证的请求
     *
     * @param url
     * @param data
     * @param connectTimeoutMs
     * @param readTimeoutMs
     * @return
     */
    public static String requestWithoutCert(String url, Map<String, String> data, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        return request(url, data, connectTimeoutMs, readTimeoutMs, false, null, null);
    }

    /**
     * 可重试的，双向认证的请求
     *
     * @param url
     * @param data
     * @return
     */
    public static String requestWithCert(String url, Map<String, String> data, InputStream certStream, String password) throws Exception {
        return request(url, data, connectTimeoutMs, readTimeoutMs, true, certStream, password);
    }

    /**
     * 可重试的，双向认证的请求
     *
     * @param url
     * @param data
     * @param connectTimeoutMs
     * @param readTimeoutMs
     * @return
     */
    public static String requestWithCert(String url, Map<String, String> data, int connectTimeoutMs, int readTimeoutMs, InputStream certStream, String password) throws Exception {
        return request(url, data, connectTimeoutMs, readTimeoutMs, true, certStream, password);
    }
}
